package asteroids.model.statements;

import asteroids.model.*;
import asteroids.model.expressions.*;
import asteroids.model.programs.parsing.ProgramFactory.ForeachType;
import asteroids.model.types.T;

import java.util.*;

/**
 * A class that represents a foreach loop
 * 
 * @version 1.0
 * 
 * @author	Thijs Peirelinck & Wouter Parys
 *
 */
public class ForEach implements NormalS {
	
	private S statement;
	private String variableName;
	private ForeachType type;
	private Ship motherShip;
	
	public ForEach(S body, String variableName, ForeachType type) {
		this.statement = body;
		this.variableName = variableName;
		this.type = type;
	}
	
	public Set getSet() {
		switch (type) {
			case ASTEROID:
				return motherShip.getWorld().getAsteroidList();
			case SHIP:
				return motherShip.getWorld().getShipList();
			case BULLET:
				return motherShip.getWorld().getBulletList();
			case ANY:
				return motherShip.getWorld().getFlyingObjectsSet();
		}
		return new HashSet<FlyingObject>();
	}

	@Override
	public Iterator<S> iterator(final HashMap<String, E> variables) {
		return new Iterator<S>() {
			
			{
				ArrayList<FlyingObject> arrayList = new ArrayList<>();
				arrayList.addAll(getSet());
				itList = arrayList;
				currentIterator = ((Sequence)statement).iterator(variables);
				counter = 0;
			}
			
			private ArrayList itList;
			private int counter;
			private Iterator<S> currentIterator;
			
			@Override
			public boolean hasNext() {
				if (itList.size() == 0) {
					return false; }
				variables.put(variableName, new EntityLiteral(((FlyingObject)itList.get(counter))));
				if (currentIterator.hasNext()) {
					return true; }
				for (int i = counter + 1; i < itList.size(); i++) {
					variables.put(variableName, new EntityLiteral(((FlyingObject)itList.get(i))));
					if (((Sequence)statement).iterator(variables).hasNext()) {
						return true;
					}
				}
				return false;
			}

			@Override
			public S next() {
				if(!hasNext()) 
					throw new NoSuchElementException();
				variables.put(variableName, new EntityLiteral(((FlyingObject)itList.get(counter))));
				if (currentIterator.hasNext()) {
					return currentIterator.next();
				}
				else {
					boolean foundNext = false;
					for (int i = counter + 1; !foundNext && i<itList.size(); i++) {
						variables.put(variableName, new EntityLiteral(((FlyingObject)itList.get(i))));
						currentIterator = ((Sequence)statement).iterator(variables);						
						counter++;
						if (currentIterator.hasNext()) {
							foundNext = true;
						}
					}
					return currentIterator.next();					
				}
			}

			@Override
			public void remove() throws UnsupportedOperationException {
				throw new UnsupportedOperationException();
			}
			
		};
	}

	@Override
	public void setExecuter(Ship ship) {
		statement.setExecuter(ship);
		motherShip = ship;
	}

	@Override
	public boolean typeCheck(Map<String, T> globals) {
		if(globals.get(variableName) != null) {
			boolean test1 = globals.get(variableName).getReturnType().equals("entity");
			boolean test2 = statement.typeCheck(globals);
			boolean test3 = this.containsAction();
			return test1 && test2 && !test3;
		}
		return false;
	}
	
	@Override
	public boolean containsAction() {
		return ((NormalS)statement).containsAction();
	}
}
